package scala.examples.tcpoly.dgp.gm;

trait Either[a,b]
  case class Inl[a,b](el: a) extends Either[a,b]
  case class Inr[a,b](el: b) extends Either[a,b]


trait GenericCore {  
  trait Generic[g[x]] {
    //  cases for fixed set of types:
    def unit: g[Unit]; def char: g[Char]; def int: g[Int]
    def either[a <% Rep[a], b <% Rep[b]]: g[Either[a, b]]
    def both[a <% Rep[a], b <% Rep[b]] : g[Pair[a, b]]
    def datatype[a <% Rep[a], b](iso: Iso[a,b]): g[b]
  }
  
  case class Iso[t,dt](fromData: dt=>t, toData: t=>dt) 
  
  trait Rep[t]{ def rep[m[x]](implicit gen: Generic[m]): m[t] }
  
  // a function from any representable type to Result
  trait SimpleGenfun[Result] extends Generic[SGF[Result]#gf] {
    def apply[t <% Rep[t]](x: t) = x.rep(this)(x)
    def datatype[a <% Rep[a], b](iso: Iso[a,b]) 
      = (x: b) => this(iso.fromData(x))    
  } 
  trait SGF[Result]{type gf[a] = a => Result}
}


trait CoreConvs requires (GenericCore with CoreConvs) {
  implicit def unit2rep(u: Unit): Rep[Unit] = new Rep[Unit]{def rep[g[x]](implicit gen: Generic[g]): g[Unit]= gen.unit }//  RepI (.unit)
  implicit def int2rep(i: Int): Rep[Int] = new Rep[Int]{def rep[g[x]](implicit gen: Generic[g]): g[Int]= gen.int } //RepI (.int)
  implicit def char2rep(c: Char): Rep[Char] = new Rep[Char]{def rep[g[x]](implicit gen: Generic[g]): g[Char]= gen.char } //RepI (.char)
  implicit def pair2rep[t <% Rep[t], u <% Rep[u]](p: Pair[t, u]): Rep[Pair[t, u]] 
      = new Rep[Pair[t, u]]{def rep[g[x]](implicit gen: Generic[g]): g[Pair[t, u]]= gen.both } //RepI (.both)
  implicit def plus2rep[t <% Rep[t], u <% Rep[u]](p: Either[t, u]): Rep[Either[t, u]]  
      = new Rep[Either[t, u]]{def rep[g[x]](implicit gen: Generic[g]): g[Either[t, u]]= gen.either } //RepI (.either)

  implicit def list2rep[t <% Rep[t]](l: List[t]): Rep[List[t]] = {
    def fromList[t](l: List[t]): Either[Unit, Pair[t, List[t]]] = l match {
      case Nil => Inl(())
      case x :: xs => Inr(Pair(x, xs))
    }
      
    def toList[t](l: Either[Unit, Pair[t, List[t]]]): List[t] = l match {
      case Inl(())=> Nil 
      case Inr(Pair(x, xs)) => x :: xs 
    }
    new Rep[List[t]]{def rep[g[x]](implicit gen: Generic[g]): g[List[t]]= gen.datatype(Iso(fromList[t], toList[t])) } //RepI (.datatype(Iso(fromList[t], toList[t])))  // TODO: have to supply [t], why?
  }
}
 
trait GenericShowBin requires (GenericCore with CoreConvs with GenericShowBin) {
  type Bin = List[Bit]
  object showBin extends SimpleGenfun[Bin] {
    def unit = ((x: Unit) => Nil) 
    def either[a <% Rep[a], b <% Rep[b]] = (x: Either[a, b]) => x match { 
      case Inl(el) => _0 :: this(el)
      case Inr(el) => _1 :: this(el) }
    def both[a <% Rep[a], b <% Rep[b]] = (x: Pair[a, b]) =>
      this(x._1) ::: this(x._2)
    def char = (x: Char) => bits(7,x)
    def int = (x: Int) => bits(16,x)
  }

  trait Bit
    case object _0 extends Bit
    case object _1 extends Bit
  def bits(n: Int, x: Int): Bin = if(n==0) Nil else {if((x&1)==1) _1 else _0} :: bits(n-1,x>>1)
}

object Test extends GenericCore with CoreConvs with GenericShowBin with Application {
  Console.println(showBin(List(1,2,3)))  
}
